﻿Imports System.Text.RegularExpressions
Imports System.Workflow.Runtime
Imports System.Threading
Imports Pronama.NicoVideo.LiveStreaming
Imports Pronama.Sekatyaku.ViewModel
Imports Pronama.Sekatyaku.Model
Imports System.Windows.Media.Animation
Imports System.Reflection
Imports System.Net
Imports Hal.CookieGetterSharp

Class MainWindow

    Private LiveProgramClient As LiveProgramClient
    Private Instance As WorkflowInstance
    Private WaitHandle As New AutoResetEvent(False)
    Private ConnectionEvent As New ManualResetEvent(False)
    Private IsWokrflowRunning As Boolean

    Private SelectedCookieGetter As ICookieGetter

    Private Settings As New SettingsWindowViewModel
    Private StartedPlayKind As PlayKind

    Private SecondsBeforeStart As Integer
    Private SecondsBeforeEnd As Integer

    Protected ReadOnly Property ViewModel As MainWindowViewModel
        Get
            Return DirectCast(Me.DataContext, MainWindowViewModel)
        End Get
    End Property

    Public Sub New()
        ' この呼び出しはデザイナーで必要です。
        InitializeComponent()

        Dim getters = CookieGetter.CreateInstances(True)
        For Each cg In getters
            CookieComboBox.Items.Add(cg)
        Next

        ' InitializeComponent() 呼び出しの後で初期化を追加します。
        LoadSettings()

    End Sub

    Private Function GetVideoIds(ByVal text As String) As String()
        Dim matchs = Regex.Matches(text,
                                    "\b(?<id>(sm|nm)\d+)\b", RegexOptions.ECMAScript)
        Dim list = New List(Of String)
        For Each m As Match In matchs
            Dim id = m.Groups("id").Value
            If list.Contains(id) Then
                Continue For
            End If
            list.Add(id)
        Next
        Return list.ToArray

    End Function

    Private Sub AddVideos(ByVal html As String)
        For Each id In GetVideoIds(html)
            Dim videoId = id
            Dim vs = From v In Me.ViewModel.Videos
                     Where v.Id = videoId

            If vs.Count > 0 Then
                Continue For
            End If

            Dim video = New Video With {.Id = id}
            Dim viewModel = New VideoViewModel(video)
            viewModel.DownloadInfoAsync()
            Me.ViewModel.Videos.Add(viewModel)
        Next
    End Sub

    Private Sub StartWorkflow()

        ConnectionEvent.WaitOne()
        If LiveProgramClient Is Nothing OrElse Not LiveProgramClient.Connected Then
            Exit Sub
        End If

        Using workflowRuntime As New WorkflowRuntime()
            AddHandler workflowRuntime.WorkflowCompleted, AddressOf OnWorkflowCompleted
            AddHandler workflowRuntime.WorkflowTerminated, AddressOf OnWorkflowTerminated

            IsWokrflowRunning = True



            Dim dic = New Dictionary(Of String, Object) From
                {{"Videos", Videos.ToArray},
                 {"LiveProgramClient", LiveProgramClient},
                 {"Comments", Me.Comments},
                 {"PlayKind", Me.PlayKind},
                 {"SecondsBeforeStart", Me.SecondsBeforeStart},
                 {"SecondsBeforeEnd", Me.SecondsBeforeEnd}}

            Dim workflowInstance As WorkflowInstance

            workflowInstance = workflowRuntime.CreateWorkflow(GetType(MainWorkflow), dic)
            Instance = workflowInstance
            workflowInstance.Start()

            WaitHandle.WaitOne()

            Instance = Nothing
            IsWokrflowRunning = False
        End Using

    End Sub

    Private Sub OnWorkflowCompleted(ByVal sender As Object, ByVal e As WorkflowCompletedEventArgs)
        WaitHandle.Set()
    End Sub

    Private Sub OnWorkflowTerminated(ByVal sender As Object, ByVal e As WorkflowTerminatedEventArgs)
        WaitHandle.Set()
    End Sub

#Region "Window events"

    Private Sub MainWindow_Closed(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Closed
        Try
            Shutdown()
            SaveSettings()
        Catch ex As Exception
#If DEBUG Then
            Stop
#End If
        End Try
    End Sub

    Private Sub MainWindow_Drop(ByVal sender As Object, ByVal e As System.Windows.DragEventArgs) Handles Me.Drop
        Dim html = e.Data.GetData(DataFormats.Html)
        If html IsNot Nothing Then
            AddVideos(CStr(html))
        End If

        If ViewModel.Videos.Count > 0 Then
            DescriptionGrid.Visibility = Windows.Visibility.Collapsed
        End If
    End Sub


    Private Sub MainWindow_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Input.KeyEventArgs) Handles Me.KeyDown

        If e.Key = Key.Delete AndAlso VideosListView.SelectedItems.Count > 0 Then

            Dim index = VideosListView.SelectedIndex

            For i = VideosListView.SelectedItems.Count - 1 To 0 Step -1
                ViewModel.Videos.Remove(DirectCast(VideosListView.SelectedItems(i), VideoViewModel))
            Next

            If VideosListView.Items.Count - 1 < index Then
                VideosListView.SelectedIndex = VideosListView.Items.Count - 1
            Else
                VideosListView.SelectedIndex = index
            End If

        ElseIf e.Key = Key.V AndAlso Keyboard.Modifiers = ModifierKeys.Control AndAlso Not e.IsRepeat Then

            Try
                Dim html = CStr(Clipboard.GetData(DataFormats.Html))
                If html IsNot Nothing Then
                    AddVideos(html)
                Else
                    Dim text = Clipboard.GetText
                    AddVideos(text)
                End If
            Catch ex As Exception
                ' Ignore
            End Try

            If ViewModel.Videos.Count > 0 Then
                DescriptionGrid.Visibility = Windows.Visibility.Collapsed
            End If
        End If
    End Sub

#End Region

#Region "ListView Drag & Drop"

    ' ref: http://palepoli.skr.jp/content/wpf/dragdrop.php

    Private OldIndex As Integer = -1
    Private Delegate Function GetPositionDelegate(ByVal element As IInputElement) As Point

#Region "listView_Util"

    Private Function GetListViewItem(ByVal index As Integer) As ListViewItem

        If VideosListView.ItemContainerGenerator.Status <> System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated Then
            Return Nothing
        End If

        Return TryCast(VideosListView.ItemContainerGenerator.ContainerFromIndex(index), ListViewItem)
    End Function

    Private Function GetCurrentIndex(ByVal getPosition As GetPositionDelegate) As Integer
        Dim index As Integer = -1
        For i As Integer = 0 To Me.VideosListView.Items.Count - 1
            Dim item As ListViewItem = GetListViewItem(i)
            If Me.IsMouseOverTarget(item, getPosition) Then
                index = i
                Exit For
            End If
        Next
        Return index
    End Function

    Private Function IsMouseOverTarget(ByVal target As Visual, ByVal getPosition As GetPositionDelegate) As Boolean
        If target Is Nothing Then
            Return False
        End If

        Dim bounds As Rect = VisualTreeHelper.GetDescendantBounds(target)
        Dim mousePos As Point = getPosition(DirectCast(target, IInputElement))
        Return bounds.Contains(mousePos)
    End Function

#End Region

    Private Sub VideosListView_Drop(ByVal sender As Object, ByVal e As DragEventArgs) Handles VideosListView.Drop
        For i = 0 To Me.VideosListView.Items.Count - 1
            Dim item = Me.GetListViewItem(i)
            If item IsNot Nothing Then
                item.Opacity = 1.0
            End If
        Next
        Dim newIndex = Me.GetCurrentIndex(AddressOf e.GetPosition)
        If OldIndex <> newIndex AndAlso newIndex > -1 Then

            If OldIndex < 0 OrElse OldIndex >= ViewModel.Videos.Count Then
                Exit Sub
            End If

            Dim it = ViewModel.Videos(OldIndex)
            ViewModel.Videos.RemoveAt(OldIndex)
            ViewModel.Videos.Insert(newIndex, it)
            VideosListView.SelectedIndex = newIndex
        End If
        'VideosListView.AllowDrop = False
        OldIndex = -1
    End Sub

    Private Sub VideosListView_DragOver(ByVal sender As Object, ByVal e As DragEventArgs) Handles VideosListView.DragOver
        If OldIndex < 0 Then
            Exit Sub
        End If

        Dim n = Me.GetCurrentIndex(AddressOf e.GetPosition)
        For i = 0 To Me.VideosListView.Items.Count - 1
            If i = n AndAlso i <> OldIndex Then
                Dim item = Me.GetListViewItem(i)
                If item IsNot Nothing Then
                    item.Opacity = 0.3
                End If
            Else
                Dim item = Me.GetListViewItem(i)
                If item IsNot Nothing Then
                    item.Opacity = 1.0
                End If
            End If
        Next
    End Sub

    Private Sub VideosListView_PreviewMouseLeftButtonDown(ByVal sender As Object, ByVal e As MouseButtonEventArgs) Handles VideosListView.PreviewMouseLeftButtonDown
        OldIndex = Me.GetCurrentIndex(AddressOf e.GetPosition)
        If OldIndex < 0 Then
            Exit Sub
        End If
        VideosListView.SelectedIndex = OldIndex
        'VideosListView.AllowDrop = True
        DragDrop.DoDragDrop(VideosListView, VideosListView.Items(OldIndex), DragDropEffects.Move)
    End Sub

#End Region

    ' ref: http://dotnetlogbook.blogspot.com/2009/08/wpf.html
    Private Sub ShowStatusMessage(ByVal message As String)
        If Not Me.Dispatcher.CheckAccess Then
            Me.Dispatcher.Invoke(New Action(Of String)(AddressOf ShowStatusMessage), New Object() {message})
            Exit Sub
        End If
        StatusBarTextBlock.Text = message
        Dim s = DirectCast(StatusBarTextBlock.Resources("FadeStoryboard"), Storyboard)
        s.Begin()
    End Sub


    Private Sub CommentServer_ConnectionStatusChanged(ByVal sender As Object, ByVal e As EventArgs)
        Select Case LiveProgramClient.Connected
            Case True
                ConnectionEvent.Set()
                ShowStatusMessage("接続しました。")

            Case False
                ConnectionEvent.Set()
                If IsWokrflowRunning AndAlso Instance IsNot Nothing Then
                    Instance.Terminate("Disconnected")
                End If
                ShowStatusMessage("切断しました。")
        End Select
    End Sub

    Private Videos As New List(Of Video)

    Private Sub Button_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)

        If ViewModel.Videos.Count = 0 Then
            Exit Sub
        End If

        If CookieComboBox.SelectedItem Is Nothing Then
            Exit Sub
        End If

        SelectedCookieGetter = DirectCast(CookieComboBox.SelectedItem, ICookieGetter)
        Dim userSession As Cookie = Nothing
        Try
            userSession = SelectedCookieGetter.GetCookie(New Uri("http://live.nicovideo.jp"), "user_session")
        Catch ex As Exception
            MessageBox.Show("Cookie の取得に失敗しました。")
            Exit Sub
        End Try
        If userSession Is Nothing Then
            MessageBox.Show("Cookie の取得に失敗しました。")
            Exit Sub
        End If


        ' ViewModel は別スレッドから参照できないので Video のみ取得しておく
        Videos.Clear()
        For Each v In ViewModel.Videos
            Videos.Add(v.ToModel)
        Next

        StartedPlayKind = Me.PlayKind

        Dim match = Regex.Match(LiveIdTextBox.Text, "(?<id>lv\d+)")
        If Not match.Success Then
            Exit Sub
        End If
        Dim id = match.Groups("id").Value


        If LiveProgramClient IsNot Nothing Then
            RemoveHandler LiveProgramClient.ConnectedChanged, AddressOf CommentServer_ConnectionStatusChanged
            If LiveProgramClient.Connected Then
                LiveProgramClient.Close()
            Else
                ' Connecting の場合
                ConnectionEvent.Set()
            End If
        End If

        ConnectionEvent.Reset()

        LiveProgramClient = New LiveProgramClient
        AddHandler LiveProgramClient.ConnectedChanged, AddressOf CommentServer_ConnectionStatusChanged

        Try
            LiveProgramClient.Connect(id, userSession)
        Catch ex As Exception
            MessageBox.Show("接続に失敗しました。")
        End Try


        Dim thread = New System.Threading.Thread(AddressOf StartWorkflow)
        thread.Start()
    End Sub

    Private Sub DisconnectButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
        Shutdown()
    End Sub

    Private Sub Shutdown()
        If IsWokrflowRunning AndAlso Instance IsNot Nothing Then
            Instance.Terminate("Disconnect by user")
        End If

        If LiveProgramClient IsNot Nothing Then

            If LiveProgramClient.Connected Then
                Select Case StartedPlayKind
                    Case Sekatyaku.PlayKind.Play, Sekatyaku.PlayKind.PlaySound
                        LiveProgramClient.BroadcastComment("/stop")
                    Case Sekatyaku.PlayKind.PlaySub, Sekatyaku.PlayKind.PlaySoundSub
                        LiveProgramClient.BroadcastComment("/stop sub")
                    Case Else
                        LiveProgramClient.BroadcastComment("/stop")

                End Select
                LiveProgramClient.BroadcastComment("/clear")
                LiveProgramClient.Close()
            Else
                ' Connecting 用
                ConnectionEvent.Set()
            End If
        End If

        LiveProgramClient = Nothing
    End Sub

    '#Region "Menu items"
    '    ' TODO refactoring
    '    Private Sub IEMenuItem_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    '        CookieSource = NicoLiveService.CookieSource.InternetExplorer
    '        IEMenuItem.IsChecked = True
    '        FirefoxMenuItem.IsChecked = False
    '        GoogleChromeMenuItem.IsChecked = False
    '        OperaMenuItem.IsChecked = False
    '    End Sub

    '    Private Sub FirefoxMenuItem_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    '        CookieSource = NicoLiveService.CookieSource.Firefox
    '        IEMenuItem.IsChecked = False
    '        FirefoxMenuItem.IsChecked = True
    '        GoogleChromeMenuItem.IsChecked = False
    '        OperaMenuItem.IsChecked = False

    '    End Sub

    '    Private Sub GoogleChromeMenuItem_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    '        CookieSource = NicoLiveService.CookieSource.GoogleChrome
    '        IEMenuItem.IsChecked = False
    '        FirefoxMenuItem.IsChecked = False
    '        GoogleChromeMenuItem.IsChecked = True
    '        OperaMenuItem.IsChecked = False

    '    End Sub

    '    Private Sub OperaMenuItem_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    '        CookieSource = NicoLiveService.CookieSource.Opera
    '        IEMenuItem.IsChecked = False
    '        FirefoxMenuItem.IsChecked = False
    '        GoogleChromeMenuItem.IsChecked = False
    '        OperaMenuItem.IsChecked = True

    '    End Sub

    '#End Region

    Private Sub MenuItem_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)

        Dim window = New SettingsWindow
        window.PlayKind = Me.PlayKind
        window.SecondsBeforeStart = Me.SecondsBeforeStart
        window.SecondsBeforeEnd = Me.SecondsBeforeEnd
        window.Owner = Me

        For Each c In Comments
            window.Comments(c.Key).Text = c.Value.Text
            window.Comments(c.Key).Mail = c.Value.Mail
        Next

        If window.ShowDialog <> True Then
            Exit Sub
        End If

        For Each c In window.Comments
            Comments(c.Key).Text = c.Value.Text
            Comments(c.Key).Mail = c.Value.Mail
        Next

        PlayKind = window.PlayKind
        SecondsBeforeStart = window.SecondsBeforeStart
        SecondsBeforeEnd = window.SecondsBeforeEnd

        SaveSettings()

    End Sub

    Private Comments As New Dictionary(Of BroadcastCommentKind, BroadcastComment)
    Private PlayKind As PlayKind

    ' TODO settings 

    Private Sub LoadSettings()

        ' Default
        Dim models = New List(Of BroadcastComment)
        Comments.Add(BroadcastCommentKind.Opening,
                     New BroadcastComment With {.Kind = BroadcastCommentKind.Opening,
                                                .Text = "<font color=""#FF9900"">異世界の新着動画</font><br><font color=""#C0C0C0"">Powered by 異世界の新着動画 (co9320)<font>",
                                                .Mail = "hidari"})

        Comments.Add(BroadcastCommentKind.VideoInfo,
                     New BroadcastComment With {.Kind = BroadcastCommentKind.VideoInfo,
                                              .Text = "<font color=""#FF9900"">投稿日時:{date}</font><br>タイトル:{title}<br><font color=""#C0C0C0"">{desc}<font>",
                                              .Mail = "hidari"})

        Comments.Add(BroadcastCommentKind.VotingStartCountDown,
                     New BroadcastComment With {.Kind = BroadcastCommentKind.VotingStartCountDown,
                                              .Text = "後 {sec} 秒で、『延長アンケート』を開始いたします。",
                                              .Mail = "hidden"})

        Comments.Add(BroadcastCommentKind.VotingStarted,
                     New BroadcastComment With {.Kind = BroadcastCommentKind.VotingStarted,
                                              .Text = "『延長アンケート』&#13;引き続きこの動画を視聴しますか？（過半数以上の支持で延長） はい いいえ",
                                              .Mail = "hidari hidden"})

        Comments.Add(BroadcastCommentKind.VotingEndCountDown,
                     New BroadcastComment With {.Kind = BroadcastCommentKind.VotingEndCountDown,
                                              .Text = "『延長アンケート』（締め切りまで後 {sec} 秒です。）<br>引き続きこの動画を視聴しますか？（過半数以上の支持で延長）",
                                              .Mail = "hidden"})

        Comments.Add(BroadcastCommentKind.VotingEnded,
                     New BroadcastComment With {.Kind = BroadcastCommentKind.VotingEnded,
                                              .Text = "『延長アンケート』（投票は締め切りました）<br>引き続きこの動画を視聴しますか？（過半数以上の支持で延長）",
                                              .Mail = "hidari hidden"})


        ' Load
        Try
            Dim location = Assembly.GetEntryAssembly().Location
            Dim file = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(location), "iSekatyakuSettings.xml")
            If Not System.IO.File.Exists(file) Then
                Exit Sub
            End If
            Dim doc = XDocument.Load(file)

            Me.PlayKind = DirectCast(Convert.ToInt32(doc.<sekatyaku>.<playKind>.Value), PlayKind)

            Dim secondsBeforeStart As Integer = 0
            If Integer.TryParse(doc.<sekatyaku>.<secondsBeforeStart>.Value, secondsBeforeStart) Then
                Me.SecondsBeforeStart = secondsBeforeStart
            End If

            Dim secondsBeforeEnd As Integer = 0
            If Integer.TryParse(doc.<sekatyaku>.<secondsBeforeEnd>.Value, secondsBeforeEnd) Then
                Me.SecondsBeforeEnd = secondsBeforeEnd
            End If

            For Each item In CookieComboBox.Items
                Dim cg = DirectCast(item, ICookieGetter)
                If doc.<sekatyaku>.<cookieSource>.Value = cg.Status.DisplayName Then
                    CookieComboBox.SelectedItem = item
                    SelectedCookieGetter = cg
                    Exit For
                End If
            Next
            'Me.CookieSource = DirectCast(Convert.ToInt32(doc.<sekatyaku>.<cookieSource>.Value), CookieSource)


            For Each e In doc...<comment>
                Dim kind = DirectCast(Convert.ToInt32(e.<kind>.Value), BroadcastCommentKind)
                Comments(kind).Text = e.<text>.Value
                Comments(kind).Mail = e.<mail>.Value
            Next

        Catch ex As Exception
#If DEBUG Then
            Stop
#End If
        End Try

    End Sub

    Private Sub SaveSettings()
        Try
            Dim location = Assembly.GetEntryAssembly().Location
            Dim file = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(location), "iSekatyakuSettings.xml")

            Dim doc = <?xml version="1.0" encoding="utf-8"?>
                      <sekatyaku>
                          <cookieSource><%= If(SelectedCookieGetter IsNot Nothing, SelectedCookieGetter.Status.DisplayName, "") %></cookieSource>
                          <playKind><%= Convert.ToInt32(Me.PlayKind) %></playKind>
                          <SecondsBeforeStart><%= Me.SecondsBeforeStart %></SecondsBeforeStart>
                          <secondsBeforeEnd><%= Me.SecondsBeforeEnd %></secondsBeforeEnd>
                          <comments/>
                      </sekatyaku>

            For Each c In Comments.Values
                Dim e = <comment>
                            <kind><%= Convert.ToInt32(c.Kind) %></kind>
                            <text><%= c.Text %></text>
                            <mail><%= c.Mail %></mail>
                        </comment>

                doc.<sekatyaku>.First.Element("comments").Add(e)
            Next

            doc.Save(file)

        Catch ex As Exception
#If DEBUG Then
            Stop
#End If

        End Try
    End Sub

    Private Sub Videos_CollectionChanged(ByVal sender As Object, ByVal e As Specialized.NotifyCollectionChangedEventArgs)

        If e.NewItems IsNot Nothing Then
            For Each i As VideoViewModel In e.NewItems
                AddHandler i.DownloadInfoCompleted, AddressOf VideoViewModel_DownloadInfoCompleted
            Next
        End If

        If e.OldItems IsNot Nothing Then
            For Each i As VideoViewModel In e.OldItems
                RemoveHandler i.DownloadInfoCompleted, AddressOf VideoViewModel_DownloadInfoCompleted
            Next

            VideoViewModel_DownloadInfoCompleted(Me, EventArgs.Empty)
        End If

    End Sub

    Private Sub VideoViewModel_DownloadInfoCompleted(ByVal sender As Object, ByVal e As EventArgs)
        If Not Me.Dispatcher.CheckAccess Then
            Me.Dispatcher.Invoke(New Action(Of Object, EventArgs)(AddressOf VideoViewModel_DownloadInfoCompleted), New Object() {sender, e})
            Exit Sub
        End If

        Dim count = ViewModel.Videos.Count
        Dim total = ViewModel.Videos.Sum(Function(v As VideoViewModel) v.Length.TotalSeconds)

        Dim m = Convert.ToInt32(Math.Floor(total)) \ 60
        Dim s = Convert.ToInt32(total - m * 60)

        RightSideStatusBarTextBox.Text = String.Format("動画数: {0}, 合計時間: {1}:{2}", count, m, s)

    End Sub

    Private Sub MainWindow_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
        ' TODO
        AddHandler ViewModel.Videos.CollectionChanged, AddressOf Videos_CollectionChanged

    End Sub

    Private Sub OpenUri(ByVal uri As Uri)
        Try
            Process.Start(uri.ToString)
        Catch ex As Exception

        End Try
    End Sub

    Private Sub DownloadSiteMenuItem_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
        OpenUri(New Uri("http://sekatyaku.codeplex.com/"))
    End Sub

    Private Sub SupportMenuItem_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
        OpenUri(New Uri("http://ch.nicovideo.jp/community/co9320/"))
    End Sub


    Private Sub Hyperlink_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
        OpenUri(New Uri("http://www.nicovideo.jp/watch/sm9893018"))
    End Sub
End Class
